package pprg.core;

import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.TreeMap;

public class PerformanceTest implements Observer {

	static int arraySize = 1000;;
	static int ITERATIONS = 100;
	private long parallelEndTime;
	private boolean parallelCalculationFinished;

	private boolean[][] originalArray;
	private GameOfLife gol;

	private static int numberRepeatingsPerCalculation = 10;

	public PerformanceTest(GameOfLife gol) {
		this.gol = gol;
		this.originalArray = gol.getArray();
		gol.addObserver(this);
	}

	public static void main(String[] args) {
		measurePerformance();
		measureArraySizeTimeEffect();
	}

	/**
	 * Measure Time Performance for 1,2,4,8...256 threads. The number of
	 * iterations and the array size can be configured via the global
	 * attributes. Also the number of repatings for each calculation can be
	 * configured.
	 */
	private static void measurePerformance() {
		GameOfLife gol = new GameOfLife(arraySize, 0.1);
		PerformanceTest performaceTest = new PerformanceTest(gol);

		int[] threadNumbers = { 1, 2, 4, 8, 16, 32, 64, 128, 256 };
		Map<Integer, Long> sumTimesPerThread = new HashMap<Integer, Long>();

		for (int i = 0; i < numberRepeatingsPerCalculation; i++) {
			for (int j = 0; j < threadNumbers.length; j++) {
				int th = threadNumbers[j];
				performaceTest.reset();
				long time;
				if (th == 1) {
					time = performaceTest
							.measureSequentialCalculation(ITERATIONS);
				} else {
					time = performaceTest.measureParallelCalculation(
							ITERATIONS, th);
				}
				if (!sumTimesPerThread.containsKey(threadNumbers[j])) {
					sumTimesPerThread.put(th, time);
				} else {
					long sumTime = sumTimesPerThread.get(th) + time;
					sumTimesPerThread.put(th, sumTime);
				}
			}
		}

		Map<Integer, Long> avgTimes = new TreeMap<Integer, Long>();
		for (int th : sumTimesPerThread.keySet()) {
			avgTimes.put(th, sumTimesPerThread.get(th)
					/ numberRepeatingsPerCalculation);
		}

		Util.writeToCSVFile(String.format(
				"Performance_ArraySize_%s_Iterations_%s.csv", arraySize,
				ITERATIONS), avgTimes, numberRepeatingsPerCalculation);

	}

	/**
	 * Measere array size effects. always double the array size 10,20,40...10240
	 * Calculate with single thread and double thread version. spendedn time
	 * should be protortional to arraysize²
	 */
	private static void measureArraySizeTimeEffect() {
		Map<Integer, Long> timesSequential = new TreeMap<Integer, Long>();
		Map<Integer, Long> timesParallel2 = new TreeMap<Integer, Long>();
		Map<Integer, Long> timesParallel4 = new TreeMap<Integer, Long>();
		arraySize = 10;

		for (int i = 0; i < 10; i++) {
			GameOfLife gol = new GameOfLife(arraySize, 0.1);
			PerformanceTest p = new PerformanceTest(gol);
			long timeSeq = p.measureSequentialCalculation(100);
			timesSequential.put(arraySize, timeSeq);
			p.reset();
			long timePar2 = p.measureParallelCalculation(100, 2);
			timesParallel2.put(arraySize, timePar2);
			p.reset();
			long timePar4 = p.measureParallelCalculation(100, 4);
			timesParallel4.put(arraySize, timePar4);
			arraySize *= 2;
		}
		Util.writeToCSVFile("Seq_ArraySize_Test", timesSequential, 1);
		Util.writeToCSVFile("Par_2_ArraySize_Test", timesParallel2, 1);
		Util.writeToCSVFile("Par_4_ArraySize_Test", timesParallel4, 1);
	}

	private long measureSequentialCalculation(int iterations) {
		long startTimeSeq = System.currentTimeMillis();
		for (int i = 0; i < iterations; i++) {
			gol.sequentialIteration(false);
		}
		long endTimeSeq = System.currentTimeMillis();
		return endTimeSeq - startTimeSeq;
	}

	private long measureParallelCalculation(int iterations, int numThreads) {
		parallelCalculationFinished = false;
		long parallelStartTime = System.currentTimeMillis();
		gol.parallelGameOfLife(numThreads, false, iterations);
		do {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} while (!parallelCalculationFinished);
		return parallelEndTime - parallelStartTime;
	}

	@Override
	public void update(Observable o, Object arg) {
		if (arg instanceof Long) {
			parallelEndTime = (long) arg;
			parallelCalculationFinished = true;
		}
	}

	public void reset() {
		gol.setArray(originalArray);
	}
}
